HEX
Server: Apache/2.4.68 (Debian)
System: Linux as-cs-widget-demo-us-central1 6.1.0-44-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.164-1 (2026-03-09) x86_64
User: root (0)
PHP: 8.2.32
Disabled: NONE
Upload Files
File: /var/www/kevin-demo/wp-content/plugins/grocerslist/includes/Admin/PostGating.php
<?php

namespace GrocersList\Admin;

use GrocersList\Support\Config;

class PostGating
{
    public const META_POST_GATED = 'grocers_list_post_gated';
    private const META_RECIPE_CARD_GATED = 'grocers_list_recipe_card_gated';
    private const META_NO_GATING = 'grocers_list_no_gating';

    public function register(): void
    {
        add_action('add_meta_boxes', [$this, 'addMetaBoxes']);
        add_action('save_post', [$this, 'savePostMeta']);
        add_action('init', [$this, 'registerPostMeta']);
    }

    public function registerPostMeta(): void
    {
        register_post_meta('post', self::META_POST_GATED, [
            'show_in_rest' => true,
            'single' => true,
            'type' => 'string',
            'auth_callback' => function () {
                return current_user_can('edit_posts');
            }
        ]);

        register_post_meta('post', self::META_RECIPE_CARD_GATED, [
            'show_in_rest' => true,
            'single' => true,
            'type' => 'string',
            'auth_callback' => function () {
                return current_user_can('edit_posts');
            }
        ]);

        register_post_meta('post', self::META_NO_GATING, [
            'show_in_rest' => true,
            'single' => true,
            'type' => 'string',
            'auth_callback' => function () {
                return current_user_can('edit_posts');
            }
        ]);
    }

    public function addMetaBoxes(): void
    {
        // If external JS URL is not set, don't show the meta box
        $externalJsUrl = Config::getExternalJsUrl();
        if (empty($externalJsUrl)) {
            return;
        }

        add_meta_box(
            'grocers_list_gating_options',
            'GRO Membership Options',
            [$this, 'renderMetaBox'],
            'post',
            'side',
            'default'
        );
    }

    public function renderMetaBox($post): void
    {
        wp_nonce_field('grocers_list_gating_options', 'grocers_list_gating_nonce');

        $post_gated = get_post_meta($post->ID, self::META_POST_GATED, true);
        $recipe_card_gated = get_post_meta($post->ID, self::META_RECIPE_CARD_GATED, true);
        $no_gating = get_post_meta($post->ID, self::META_NO_GATING, true);
        $page_gated = CategoryGating::getEffectiveGating($post->ID)['page'];
        // Determine current gating option
        $current_option = 'by_category';
        if ($no_gating === '1') {
            $current_option = 'no_gating';
        } elseif ($post_gated === '1' || $page_gated === '1') {
            $current_option = 'post';
        } elseif ($recipe_card_gated === '1') {
            $current_option = 'recipe';
        }

        // Check if category-level gating is set
        $category_gating_info = '';
        $inherited_gating_text = '';
        $has_category_gating = false;
        $categories = get_the_category($post->ID);
        
        if (!empty($categories)) {
            foreach ($categories as $category) {
                $category_gating = get_term_meta($category->term_id, CategoryGating::META_CATEGORY_GATING_TYPE, true);
                if (!empty($category_gating) && $category_gating !== 'none') {
                    $has_category_gating = true;
                    $gating_label = $category_gating === 'post' || $category_gating === 'page' ? 'entire post' : 'recipe cards only';
                    $category_gating_info .= sprintf(
                        '<li>Category "<strong>%s</strong>" gates %s</li>',
                        esc_html($category->name),
                        esc_html($gating_label)
                    );
                    
                    // Set the inherited text (use first category with gating)
                    if (empty($inherited_gating_text)) {
                        $inherited_gating_text = sprintf(
                            ' <span style="color: #2271b1; font-size: 12px;">(will gate %s from "%s")</span>',
                            esc_html($gating_label),
                            esc_html($category->name)
                        );
                    }
                }
            }
        }
        
        // If no category gating, show that too
        if (!$has_category_gating) {
            $inherited_gating_text = ' <span style="color: #666; font-size: 12px;">(no gating from categories)</span>';
        }
        ?>
        <div class="grocers-list-gating-options">
            <p><strong>Post-Level Gating Options:</strong></p>
            <p>
                <label>
                    <input type="radio" name="grocers_list_gating_option" value="by_category" <?php checked($current_option, 'by_category'); ?> />
                    Inherit from category (default)<?php echo $inherited_gating_text; ?>
                </label>
            </p>
            <p>
                <label>
                    <input type="radio" name="grocers_list_gating_option" value="no_gating" <?php checked($current_option, 'no_gating'); ?> />
                    Do not gate
                </label>
            </p>
            <p>
                <label>
                    <input type="radio" name="grocers_list_gating_option" value="post" <?php checked($current_option, 'post'); ?> />
                    Gate entire post
                </label>
            </p>
            <p>
                <label>
                    <input type="radio" name="grocers_list_gating_option" value="recipe" <?php checked($current_option, 'recipe'); ?> />
                    Gate recipe cards only
                </label>
            </p>

            <?php if (!empty($category_gating_info)) : ?>
                <hr style="margin: 15px 0;">
                <p><strong>Category-Level Gating:</strong></p>
                <ul style="margin-left: 20px; font-size: 12px; color: #666;">
                    <?php echo $category_gating_info; ?>
                </ul>
                <p style="font-size: 12px; color: #666;">
                    <em>Note: Post-level settings override category settings.</em>
                </p>
            <?php endif; ?>
        </div>
        <?php
    }

    public function savePostMeta($post_id): void
    {
        if (!isset($_POST['grocers_list_gating_nonce'])) {
            return;
        }

        if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['grocers_list_gating_nonce'])), 'grocers_list_gating_options')) {
            return;
        }

        if (defined('DOING_AUTOSAVE') && constant('DOING_AUTOSAVE')) {
            return;
        }

        if (!current_user_can('edit_post', $post_id)) {
            return;
        }

        $gating_option = isset($_POST['grocers_list_gating_option']) ? sanitize_text_field($_POST['grocers_list_gating_option']) : 'by_category';
        
        // Set meta values based on radio selection
        switch ($gating_option) {
            case 'no_gating':
                $post_gated = '0';
                $recipe_card_gated = '0';
                $no_gating = '1';
                break;
            case 'page':
                $post_gated = '1';
                $recipe_card_gated = '0';
                $no_gating = '0';
                break;
            case 'post':
                $post_gated = '1';
                $recipe_card_gated = '0';
                $no_gating = '0';
                break;
            case 'recipe':
                $post_gated = '0';
                $recipe_card_gated = '1';
                $no_gating = '0';
                break;
            case 'by_category':
            default:
                $post_gated = '0';
                $recipe_card_gated = '0';
                $no_gating = '0';
                break;
        }

        update_post_meta($post_id, self::META_POST_GATED, $post_gated);
        update_post_meta($post_id, self::META_RECIPE_CARD_GATED, $recipe_card_gated);
        update_post_meta($post_id, self::META_NO_GATING, $no_gating);
    }

    public static function isPostGated($post_id): bool
    {
        return get_post_meta($post_id, self::META_POST_GATED, true) === '1';
    }

    public static function isRecipeCardGated($post_id): bool
    {
        return get_post_meta($post_id, self::META_RECIPE_CARD_GATED, true) === '1';
    }

    /**
     * Get the effective gating type for a post
     * Checks post-level settings first, then falls back to category-level settings
     * 
     * @param int $post_id Post ID
     * @return array{post: bool, recipe: bool} Array with 'post' and 'recipe' gating status
     */
    public static function getEffectiveGating(int $post_id): array
    {
        // Check if gating is explicitly disabled at post level
        $no_gating = get_post_meta($post_id, self::META_NO_GATING, true);
        if ($no_gating === '1') {
            return ['post' => false, 'recipe' => false];
        }

        // Check post-level settings
        $post_gated = get_post_meta($post_id, self::META_POST_GATED, true);
        $recipe_card_gated = get_post_meta($post_id, self::META_RECIPE_CARD_GATED, true);

        // If post has explicit gating set, use that
        if ($post_gated === '1') {
            return ['post' => true, 'recipe' => false];
        }
        if ($recipe_card_gated === '1') {
            return ['post' => false, 'recipe' => true];
        }

        // Otherwise, check category-level settings
        $categories = get_the_category($post_id);
        if (empty($categories)) {
            return ['post' => false, 'recipe' => false];
        }
        // Initialize the default post gating to false
        $resolved_post_gating = ['post' => false, 'recipe' => false];
        // Check each category for gating settings
        // If any category has gating enabled, apply it
        foreach ($categories as $category) {
            $category_gating = get_term_meta($category->term_id, CategoryGating::META_CATEGORY_GATING_TYPE, true);
            // If the category has post gating enabled or page gating enabled, return the post gating
            if ($category_gating === 'post' || $category_gating === 'page') {
                $resolved_post_gating = ['post' => true, 'recipe' => false];
            }

            if ($category_gating === 'recipe') {
                $resolved_post_gating = ['post' => false, 'recipe' => true];
            }
        }
        // Return the resolved post gating, honoring recipe card category settings
        return $resolved_post_gating;
    }
}